fn main(){
    // rust的字符是unicode字符，4个字节
    let c :char = 'c';
    println!("任何char类型的大小: {}字节", std::mem::size_of_val(&c)); // 4字节
    // rust的字符串是utf8字符串，单个码元为1~4

    // 字符串切片的类型标识是 &str，表明不可
    
    let str1 = "blover"; // 字符串字面量是切片
    let s1 = String::from(str1); // 从切片创建String
    let str2 = &s1; // 引用String的结果是&str类型 (deref隐式强制转换，未来介绍)
    let str2 = &s1[1..4]; // 获取String的切片也是&str类型
    let str2 = &s1[1..]; // 切片引用(&str)在编译时的类型大小确定（就是一个胖指针）
    let str2 = &s1[..4];
    // let ms = &mut s1; // 错误。注意s1的切片仍是s1的不可变引用
    println!("{}", str2); // blov
    // String和&str就像C++的std::String和chat*，前者包含了很多方法；&str包含了长度信息
    
    let s2 = String::from("i你好"); // 汉字占3字节
    let str3 = &s2[1..4]; // 切片的索引是通过字节来进行，取切片时一定要注意索引落在字符边界上
    //let str3 = &s2[1..5]; // error: byte index 5 is not a char boundary; it is inside '好'
    println!("{}", str3);

    let mut s3 = String::from("a");
    s3.push_str("bc");
    s3.push('d'); // String的方法中，_str后缀标识操作字符串，否则操作字符
    s3.insert_str(s3.len(), "efg");
    let c1 = s3.pop(); // 'g'
    s3 = s3.replace("f", "hij");
    s3.truncate(s3.len() - 2); // 清除指定索引开始的后缀
    println!("{}", s3); // abcdeh
    s3.clear();
    
    let s4 = String::from("中国人");
    let chars = s4.chars(); // Unicode字符数组
    let bytes = s4.bytes(); // 底层字节数组
    for c in chars {
        print!("{} ", c); // "中 国 人 "
    }
    println!("");
    
    let s5 = String::from("ab");
    let s6 = String::from("cd");
    // 拼接操作调用了标准库的add方法。返回拼接后的字符串（当然了，携带所有权）
    /*
    impl Add<&str> for String {
        type Output = String;
    
        #[inline]
        fn add(mut self, other: &str) -> String {
            self.push_str(other);
            self
        }
    }
    */
    let s7 = s5 + &s6; // 第一个参数是String，第二个参数是&str
    let s7 = s6.clone() + "e" + "f"; // 第一个add()返回的String继续参与第二个+号
    println!("{} {}", s6, s7); // s5失去所有权，不可再用
    // 我这样理解：+号会利用了左对象的空间，所以才需要获取所有权

    let tuple = get_tuple();
    let (a, b, c) = tuple; // 使用模式匹配解构元组
    println!("{} {}", b, tuple.2); // 使用`.`运算符来访问指定元素    
    // 注意由于第二个元素失去所有权（转移给b），tuple已不可用。不过仍可以通过`.`运算符访问除了二号以外的元素 

    #[derive(Debug)] // 实现debug，为了下面的println
    struct User {
        name: String,
        age: u16
    }
    let u1 = User {
        name: String::from("ice"),
        age: 20
    };
    let name = u1.name; // 转移字段值。u1不可使用
    let a = u1.age; // 不过仍可访问未被转移的字段
    let u2 = User {
        name, // 当传入值和结构体字段同名时，可以直接使用缩略的方式进行初始化
        age: 21
    };
    let u3 = User {
        age: 22,
        ..u2 // .. 语法表明凡是我们没有显式声明的字段，全部从u2中自动获取，..u2必须在结构体的尾部使用
        // 随后u2不可再用（有成员字段失去所有权）
    };
    println!("{:?}", u3); // 使用#[derive(Debug)]对结构体进行了标记，这样才能使用{:?}的方式对其进行打印输出
    // 打印结果：User { name: "ice", age: 22 }   跟程序代码一致
    // 结构体需要我们自己实现来`Display特征`，目的是希望开发者定制化输出格式

    // 元组结构体在你希望有一个整体名称，但是又不关心里面字段的名称时将非常有用（相当于有名字的元组）
    struct Point (i32, i32, i32);
    let p1 = Point(1, 2, 3);
    println!("{} {} {}", p1.0, p1.1, p1.2); // 1, 2, 3
    // 如果你定义一个类型，但是不关心该类型的内容, 只关心它的行为时，就可以使用 单元结构体
    struct Signal;
    let sig = Signal; // 用处类似于单元类型
    
    struct Nums {a: i32, b: i32}
    let n1 = Nums {a: 1, b: 2}; // *结构体不可Copy!* 即使所有类型都可Copy
    let n2 = n1; // a和b发生了拷贝，如果有非Copy字段就转移所有权
    // println!("{}", n1.a); // 错误。n1已经移动所有权，不能尝试访问字段

    let ar1: [u8; 3] = [1, 2, 3]; // 数组类型
    let ar2 = [1; 3]; // 1重复3次
    let ar3 = [2u8; 3]; // 指定元素类型
    let ar4 = ar1; // 定长数组可Copy
    let ars: [[u8; 3]; 4] = [ar1, ar2, ar3, ar4]; // 二维数组
    println!("{:?}", ars); // [[1, 2, 3], [1, 1, 1], [2, 2, 2], [1, 2, 3]]
    let ar5: &[u8] = &ar1[1..]; // 数组切片。一般都是使用切片引用
    // 数组类型是[T; n]，数组切片是[T]，务必分清
}

fn get_tuple() -> (i32, String, f64) {
    (1, String::from("hi"), 0.1) // 返回元组
}

//我的理解：不可Copy的变量一旦作为参数（参与绑定），就会失去所有权。我们应该创建引用来使用变量，除非真的不需要原变量了
//“作为参数”包括：等号右侧，函数传参。不过传给println!并没有导致失去所有权，可能与宏有关